/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package rna;

import java.util.ArrayList;

/**
 *
 * @author Igo Amauri
 */

/**
 *
 * @Descrição
 * Recebe como parâmetro o arraylist de camadas
 *
 * Primeiro passo: Calcular o gradiente da camada de saida
 * Segundo passo: calcular os gradientes das outras camadas
 * Terceiro passo: fazer o calculo de atualização dos pesos
 *
 * Dibuia ai Leo!
 *
 */
public class BackPropagation {

    ArrayList<Camadas> arrayCamadas = new ArrayList<Camadas>();
    double saidaDesejada;
    //Taxa de aprendizagem a escolha do usuário
    double taxaAprendizagem = 0.1;

    public BackPropagation(ArrayList<Camadas> array, double saidaDoArquivo){
        arrayCamadas = array;
        saidaDesejada = saidaDoArquivo;
    }

    public void executarBackPropagation(){
        //Primeiro passo: Calcular o gradiente da camada de saida.
        obterGradienteCamadaSaida();
        //Próximo passo: Ajustar peso
        atualizarPesosSaida();
        //Próximo passo: obter gradiente das outras camadas
        obterGradienteOutrasCamadas();
        //Próximo passo: ajustar os pesos das outras camadas
        atualizarPesosOutrasCamadas();
    }

    public void obterGradienteCamadaSaida(){
        //gradiente = saida desejada menos a saida obtida, multiplicado pela derivaga da sigmoid do valor .
        int numeroDeNeuroniosSaida = arrayCamadas.get(arrayCamadas.size() -1).neuronios.size();
        for(int i=0; i<numeroDeNeuroniosSaida; i++){
            double gradienteLocal = 0.0;
            double saidaReal = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).saida;
            double somatorio = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).somatorio;            
            gradienteLocal = (saidaDesejada - saidaReal) * derivadaSigmoide(somatorio);
            arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).setGradienteLocal(gradienteLocal);
        }
    }

    public void atualizarPesosSaida(){
//        System.out.println("");
//        System.out.println("IMPRIMIR PESOS DA CAMADA DE SAÍDA");
//        System.out.println("");

        
        //For que vai controlar para qual neuronio está sendo modificado
        int numeroDeNeuroniosSaida = arrayCamadas.get(arrayCamadas.size() -1).neuronios.size();
        for(int i=0; i<numeroDeNeuroniosSaida;i++){
            int numeroPesos = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).pesos.size();

//            System.out.println("No Neuronio "+ i +" da camada zero, Existem "+ numeroPesos);

            for(int j=0;j<numeroPesos;j++){
//                System.out.println("");
//                System.out.println("Peso: " + j);

                double pesoAntigo = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).pesos.get(j);

//                System.out.println("Velho valor: " + pesoAntigo);

                double gradiente = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).getgradienteLocal();

//                System.out.println("Gradiente: " + gradiente);
                
                double saida = arrayCamadas.get(arrayCamadas.size()-2).saidas[j];
                double novoPeso = pesoAntigo + taxaAprendizagem * gradiente * saida;

//                System.out.println("Novo valor: " + novoPeso);

                arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).pesos.remove(j);
                //arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).pesos.set(j, novoPeso);
                arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).pesos.add(j, novoPeso);
                
            }

            //O bias
            double pesoBiasAntigo = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).getPesoBias();
            double gradienteParaBias = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).getgradienteLocal();
            double entradaBias = arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).Entradabias;
            double novoPesoBias = pesoBiasAntigo + taxaAprendizagem * gradienteParaBias * entradaBias;
            arrayCamadas.get(arrayCamadas.size() -1).neuronios.get(i).setPesoBias(novoPesoBias);
        }
    }

    public void obterGradienteOutrasCamadas(){

        //Identificar quantas camadas escondidas existem, e acessar cada uma delas.
        int ultima = arrayCamadas.size() -2;
        for(int i=ultima;i>=0;i--){

            //For que vai varrer todos os neuronios da camada identificada com o i.
            for(int j=0; j< arrayCamadas.get(i).neuronios.size();j++){
            double gradienteLocal = 0.0;
            double somatorio = 0.0;
                double somatorioNeuronio = arrayCamadas.get(i).neuronios.get(j).somatorio;
                double derivada = derivadaSigmoide(somatorioNeuronio);

                //For para varrer os neuronios da camada a frente da que se deseja calcular o gradiente
                //Necessário para calcular o valor do somatório da formaula
                for(int k =0;k<arrayCamadas.get(i+1).neuronios.size();k++){
                    // pega o gradiente do neuronio k da camada a frente.
                    double auxGradiente = arrayCamadas.get(i+1).neuronios.get(k).getgradienteLocal();
                    // pegar o peso da conexão do neuronio i(j) com esse neuronio k.
                    double auxPeso = arrayCamadas.get(i+1).neuronios.get(k).pesos.get(j);
                    double auxPesoBias = arrayCamadas.get(i+1).neuronios.get(k).getPesoBias();
                    somatorio = somatorio + auxGradiente * auxPeso * auxPesoBias;
                }

                gradienteLocal = derivada * somatorio;
                arrayCamadas.get(i).neuronios.get(j).setGradienteLocal(gradienteLocal);
            }


        }
    }

    public void atualizarPesosOutrasCamadas(){
//        System.out.println("Entrei no atualizarPesosOutrasCamadas");
        
        //For que controla a camada que esta sendo atualizado os pesos
        int ultima = arrayCamadas.size() -2;
//        System.out.println("Ultima camada é: " + ultima);
        for(int i = ultima;i>=0;i--){
//            System.out.println("Entrei no for");

//            System.out.println("");
//            System.out.println("IMPRIMIR PESOS DA CAMADA " + i);
//            System.out.println("");
            
            int numeroDeNeuroniosCamada = arrayCamadas.get(i).neuronios.size();
            //For que vai controlar para qual neuronio esta sendo modificado.
            for(int j = 0; j<numeroDeNeuroniosCamada;j++){
                int numeroPesos = arrayCamadas.get(i).neuronios.get(j).pesos.size();

//                System.out.println("No Neuronio "+ j +" da camada " + i + " , Existem " + numeroPesos);
                
                //For para atualizar cada peso de cada neuronio
                for(int k =0;k<numeroPesos;k++){

//                    System.out.println("");
//                    System.out.println("Peso: " + k);

                    double pesoAntigo = arrayCamadas.get(i).neuronios.get(j).pesos.get(k);

//                    System.out.println("Velho valor: " + pesoAntigo);

                    double gradiente = arrayCamadas.get(i).neuronios.get(j).getgradienteLocal();

//                    System.out.println("Gradiente: " + gradiente);

                    double saida;
                    if(i==0){
                        saida = arrayCamadas.get(i).entrada[k];
                    }
                    else{
                        saida = arrayCamadas.get(i-1).saidas[k];
                    }
                    double novoPeso = pesoAntigo + taxaAprendizagem * gradiente * saida;

//                    System.out.println("Novo valor: " + novoPeso);

                    arrayCamadas.get(i).neuronios.get(j).pesos.remove(k);
                    arrayCamadas.get(i).neuronios.get(j).pesos.add(k, novoPeso);
                }
            //Bias
                double pesoAntigoBias = arrayCamadas.get(i).neuronios.get(j).getPesoBias();
                double gradienteParaBias = arrayCamadas.get(i).neuronios.get(j).getgradienteLocal();
                double entradaBias = arrayCamadas.get(i).neuronios.get(j).Entradabias;
                double novoPesoBias = pesoAntigoBias + taxaAprendizagem * gradienteParaBias * entradaBias;
                arrayCamadas.get(i).neuronios.get(j).setPesoBias(novoPesoBias);
            }
        }
    }

    private double derivadaSigmoide(double valorFuncao) {

        Sigmoid sigmoide = new Sigmoid();

        double derivadaFuncao = 0.0;

        derivadaFuncao = ( 0.5 * (sigmoide.calcularSaida(valorFuncao)) * (1- sigmoide.calcularSaida(valorFuncao)) );

        return derivadaFuncao;
    }
}